﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing;
using System.Reflection;
using NetPE.Core;
using System.Globalization;
using NetPE.Dialogs;
using System.IO;

namespace NetPE
{
    public class StreamHexEditor : BaseEditor
    {
        enum EditMode
        {
            Hex,
            Char,
            None
        }

        VScrollBar vs;
        public StreamHexEditor()
        {
            this.Controls.Add(vs = new VScrollBar());
            vs.Scroll += new ScrollEventHandler(Scrolling);
            this.DoubleBuffered = true;
            this.Font = new Font(FontFamily.GenericMonospace, 8);
            this.SetStyle(ControlStyles.Selectable, true);

            ContextMenuStrip = new ContextMenuStrip();
            ToolStripMenuItem cpy = new ToolStripMenuItem("Copy");
            cpy.Click += new EventHandler(Copy);
            ContextMenuStrip.Items.Add(cpy);
            ToolStripMenuItem fid = new ToolStripMenuItem("Find");
            fid.Click += new EventHandler(Find);
            ContextMenuStrip.Items.Add(fid);
            ToolStripMenuItem gto = new ToolStripMenuItem("Goto");
            gto.Click += new EventHandler(Goto);
            ContextMenuStrip.Items.Add(gto);
            ToolStripMenuItem rse = new ToolStripMenuItem("Resize");
            rse.Click += new EventHandler(Resizes);
            ContextMenuStrip.Items.Add(rse);
        }

        void Scrolling(object sender, ScrollEventArgs e)
        {
            this.Refresh();
        }

        protected override void RefreshData()
        {
            selStart = -1;
            selEnd = -1;
            vs.Value = 0;
            vs.Minimum = 0;
            vs.Maximum = (int)(Object as Stream).Length / 16 + 8;
            this.Refresh();
        }


        int selStart = -1;
        int selEnd = -1;
        EditMode mode;
        bool canEdit = false;
        bool isLeft;

        protected override void OnPaint(PaintEventArgs e)
        {
            if (Object != null)
            {
                vs.Dock = DockStyle.Right;
                Stream str = Object as Stream;
                int line = (this.Height - 10) / 15;
                e.Graphics.Clear(Color.Black);

                StringFormat fmt = new StringFormat();

                int startIdx = vs.Value * 16;

                for (int i = 0; i < line; i++)
                {
                    if (startIdx + i * 16 < str.Length)
                        e.Graphics.DrawString((startIdx + i * 16).ToString("x8").ToUpper(), Font, Brushes.White, new PointF(5, 5 + i * 15));
                }

                e.Graphics.DrawLine(Pens.White, new Point(65, 0), new Point(65, this.Height - 1));

                byte[] dat = new byte[16 * line];
                str.Position = startIdx;
                str.Read(dat, 0, 16 * line);

                for (int x = 0; x < 16; x++)
                {
                    for (int y = 0; y < line; y++)
                    {
                        if (startIdx + x + y * 16 < str.Length)
                        {
                            if ((startIdx + x + y * 16) >= selStart && (startIdx + x + y * 16) <= selEnd)
                                if (canEdit && mode != EditMode.Char && isLeft)
                                    e.Graphics.FillRectangle(Brushes.DimGray, new RectangleF(70 + x * 20 - 1, 5 + y * 15, 10, 15));
                                else if (canEdit && mode != EditMode.Char)
                                    e.Graphics.FillRectangle(Brushes.DimGray, new RectangleF(70 + x * 20 + 8, 5 + y * 15, 10, 15));
                                else if ((startIdx + x + y * 16) >= selStart && (startIdx + x + y * 16) <= selEnd)
                                    e.Graphics.FillRectangle(Brushes.DimGray, new RectangleF(70 + x * 20 - 2, 5 + y * 15, 20, 15));
                            e.Graphics.DrawString(dat[x + y * 16].ToString("x2").ToUpper(), Font, Brushes.White, new PointF(70 + x * 20, 5 + y * 15));
                        }
                    }
                }

                e.Graphics.DrawLine(Pens.White, new Point(400, 0), new Point(400, this.Height - 1));

                for (int x = 0; x < 16; x++)
                {
                    for (int y = 0; y < line; y++)
                    {
                        if (startIdx + x + y * 16 < str.Length)
                        {
                            if ((startIdx + x + y * 16) >= selStart && (startIdx + x + y * 16) <= selEnd)
                                e.Graphics.FillRectangle(Brushes.DimGray, new RectangleF(410 + x * 8 + 2, 5 + y * 15, 8, 15));

                            if (dat[x + y * 16] > 32)
                                e.Graphics.DrawString((char)dat[x + y * 16] + "", Font, Brushes.White, new PointF(410 + x * 8, 5 + y * 15));
                            else
                                e.Graphics.DrawString(".", Font, Brushes.White, new PointF(410 + x * 8, 5 + y * 15));

                        }
                    }
                }
            }
        }

        private int ResolveIdx(Point pt)
        {
            if (pt.X > 400)//char
            {
                int X = (pt.X - 410) / 8;
                int Y = (pt.Y - 5) / 15;
                return vs.Value * 16 + Y * 16 + X;
            }
            else if (pt.X > 65)//hex
            {
                int X = (pt.X - 70) / 20;
                int Y = (pt.Y - 5) / 15;
                return vs.Value * 16 + Y * 16 + X;
            }
            else
            {
                return -1;
            }
        }
        private bool IsLeft(Point pt)
        {
            if (pt.X > 475)//char
            {
                return Math.Abs(Math.Truncate((pt.X - 410) / 8.0) - (pt.X - 410) / 8.0) < 0.5;
            }
            else//hex
            {
                return Math.Abs(Math.Truncate((pt.X - 70) / 20.0) - (pt.X - 70) / 20.0) < 0.5;
            }
        }

        bool mouseDown;
        protected override void OnMouseDown(MouseEventArgs e)
        {
            this.Focus();
            if (e.Button != MouseButtons.Left) return;
            if (ResolveIdx(e.Location) != -1)
            {
                mouseDown = true;
                selStart = ResolveIdx(e.Location);
                selEnd = ResolveIdx(e.Location);
                mode = e.X > 400 ? EditMode.Char : EditMode.Hex;
                canEdit = true;
                if (mode == EditMode.Hex)
                    isLeft = IsLeft(e.Location);
                this.Refresh();
            }
        }
        protected override void OnMouseUp(MouseEventArgs e)
        {
            if (mouseDown)
            {
                mouseDown = false;
                if (ResolveIdx(e.Location) != -1) selEnd = ResolveIdx(e.Location);
                this.Refresh();
            }
        }
        protected override void OnMouseMove(MouseEventArgs e)
        {
            if (mouseDown)
            {
                if (ResolveIdx(e.Location) != selEnd) canEdit = false;
                if (ResolveIdx(e.Location) != -1) selEnd = ResolveIdx(e.Location);
                this.Refresh();
            }
        }

        protected override void OnKeyPress(KeyPressEventArgs e)
        {
            if (!canEdit || selStart == -1 || selStart >= (Object as Stream).Length) return;
            Stream stream = Object as Stream;
            stream.Position = selStart;
            if (mode == EditMode.Hex && "1234567890abcdefABCDEF".Contains(e.KeyChar))
            {
                string str = stream.ReadByte().ToString("x2");
                char[] c = str.ToCharArray();
                if (isLeft)
                    c[0] = e.KeyChar;
                else
                    c[1] = e.KeyChar;
                stream.Position = selStart;
                stream.WriteByte(byte.Parse(new string(c), NumberStyles.HexNumber));
                if (isLeft) isLeft = false; else if (selStart != stream.Length - 1) { selStart++; selEnd = selStart; isLeft = true; }
            }
            else if (mode == EditMode.Char)
            {
                stream.WriteByte((byte)e.KeyChar);
                if (selStart != (Object as Stream).Length - 1) { selStart++; selEnd = selStart; }
            }
            this.Refresh();
        }

        protected override void OnResize(EventArgs e)
        {
            this.Width = 575;
            vs.Dock = DockStyle.None;
            vs.Dock = DockStyle.Right;
            this.Refresh();
        }

        private void Copy(object sender, EventArgs e)
        {
            if (selStart != -1 && selEnd != -1)
            {
                Stream str = (Object as Stream);
                byte[] cpy = new byte[selEnd - selStart + 1];
                str.Position = selStart;
                str.Read(cpy, 0, selEnd - selStart + 1);
                StringBuilder ret = new StringBuilder();
                for (int i = 0; i < cpy.Length; i++)
                {
                    ret.AppendFormat(cpy[i].ToString("X2"));
                    if (i != 0 && (i + 1) % 16 == 0) ret.Append("\r\n");
                    else if (i != 0 && (i + 1) % 8 == 0) ret.Append("  ");
                    else ret.Append(" ");
                }
                Clipboard.SetText(ret.ToString());
            }
        }

        private void Find(object sender, EventArgs e)
        {
            FindDialog f = new FindDialog();
            f.Find += new EventHandler<FindEventArgs>(OnFind);
            f.ShowDialog();
        }

        void OnFind(object sender, FindEventArgs e)
        {
            BinaryReader rdr = new BinaryReader(Object as Stream);
            for (int i = selStart; i < rdr.BaseStream.Length - e.Value.Length; i++)
            {
                rdr.BaseStream.Position = i;
                bool ok = true;
                for (int ii = 0; ii < e.Value.Length; ii++)
                    if (rdr.ReadByte() != e.Value[ii])
                    {
                        ok = false;
                        break;
                    }
                if (ok)
                {
                    selStart = i;
                    selEnd = i;
                    vs.Value = (i - i % 16) / 16;
                    this.Refresh();
                    return;
                }
            }
            MessageBox.Show("Not found!");
        }

        private void Goto(object sender, EventArgs e)
        {
            GotoDialog g = new GotoDialog();
            g.Goto += new EventHandler<GotoEventArgs>(OnGoto);
            g.ShowDialog();
        }

        void OnGoto(object sender, GotoEventArgs e)
        {
            if (e.Position >= 0 && e.Position < (Object as Stream).Length)
            {
                selStart = e.Position;
                selEnd = e.Position;
                vs.Value = (e.Position - e.Position % 16) / 16;
                this.Refresh();
            }
            else
            {
                MessageBox.Show("Invaild position!");
            }
        }

        private void Resizes(object sender, EventArgs e)
        {
            ResizeDialog r = new ResizeDialog();
            if (r.ShowDialog() != DialogResult.OK) return;
            (Object as Stream).SetLength(r.NewSize);
            selStart = -1;
            selEnd = -1;
        }
    }
}
